//
//  NSObjectSafe.m
//  GALibrary
//
//  Created by WangWenhui on 2017/8/22.
//  Copyright © 2017年 途家. All rights reserved.
//

#import "NSObjectSafe.h"
#import "NSObject+Swizzle.h"

#pragma mark - Throw Exception

@implementation NSObjectSafe

+ (void) throwException:(NSString *)message
{
    NSException *exception = [NSException exceptionWithName:@"Safe Exception" reason:message userInfo:nil];
    [[NSNotificationCenter defaultCenter] postNotificationName:kNSSafeExceptionNotification object:exception userInfo:@{@"symbols":[NSThread callStackSymbols]}];
}

@end

#pragma mark - NSArray Safe

@implementation NSArray (Safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 类方法不用在NSMutableArray里再swizz一次 */
        [NSArray swizzleClassMethod:@selector(arrayWithObject:) withMethod:@selector(hookArrayWithObject:)];
        [NSArray swizzleClassMethod:@selector(arrayWithObjects:count:) withMethod:@selector(hookArrayWithObjects:count:)];
        
        /* 数组有内容obj类型才是__NSArrayI */
        NSArray* obj = [[NSArray alloc] initWithObjects:@0, nil];
        [obj swizzleInstanceMethod:@selector(objectAtIndex:) withMethod:@selector(hookObjectAtIndex:)];
        [obj swizzleInstanceMethod:@selector(subarrayWithRange:) withMethod:@selector(hookSubarrayWithRange:)];
        
        /* iOS9 以上，没内容类型是__NSArray0 */
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0) {
            obj = [[NSArray alloc] init];
            [obj swizzleInstanceMethod:@selector(objectAtIndex:) withMethod:@selector(hookObjectAtIndex0:)];
            [obj swizzleInstanceMethod:@selector(subarrayWithRange:) withMethod:@selector(hookSubarrayWithRange:)];
        }
        
    });
}

+ (instancetype) hookArrayWithObject:(id)anObject
{
    if (anObject) {
        return [self hookArrayWithObject:anObject];
    }
    [NSObjectSafe throwException:@"NSArray arrayWithObject nil"];
    return nil;
}

+ (instancetype) hookArrayWithObjects:(const id [])objects count:(NSUInteger)cnt
{
    NSInteger index = 0;
    id objs[cnt];
    for (NSInteger i = 0; i < cnt ; ++i) {
        if (objects[i]) {
            objs[index++] = objects[i];
        }
    }
    return [self hookArrayWithObjects:objs count:index];
}

/* __NSArray0 没有元素，也不可以变 */
- (id) hookObjectAtIndex0:(NSUInteger)index
{
    return nil;
}

- (id) hookObjectAtIndex:(NSUInteger)index
{
    if (index < self.count) {
        return [self hookObjectAtIndex:index];
    }
    [NSObjectSafe throwException:[NSString stringWithFormat:@"NSArray objectAtIndex [%lu] [%lu]", (unsigned long)self.count, (unsigned long)index]];
    return nil;
}

- (NSArray *) hookSubarrayWithRange:(NSRange)range
{
    if (range.location + range.length <= self.count){
        return [self hookSubarrayWithRange:range];
    }else if (range.location < self.count){
        return [self hookSubarrayWithRange:NSMakeRange(range.location, self.count-range.location)];
    }
    [NSObjectSafe throwException:[NSString stringWithFormat:@"NSArray subarrayWithRange [%lu] [%lu-%lu]", (unsigned long)self.count, (unsigned long)range.location, (unsigned long)range.length]];
    return nil;
}

@end

#pragma mark - NSMutableArray Safe

@implementation NSMutableArray (Safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSMutableArray* obj = [[NSMutableArray alloc] init];
        //对象方法 __NSArrayM 和 __NSArrayI 都有实现，都要swizz
        [obj swizzleInstanceMethod:@selector(objectAtIndex:) withMethod:@selector(hookObjectAtIndex:)];
        
        [obj swizzleInstanceMethod:@selector(addObject:) withMethod:@selector(hookAddObject:)];
        [obj swizzleInstanceMethod:@selector(insertObject:atIndex:) withMethod:@selector(hookInsertObject:atIndex:)];
        [obj swizzleInstanceMethod:@selector(removeObjectAtIndex:) withMethod:@selector(hookRemoveObjectAtIndex:)];
        [obj swizzleInstanceMethod:@selector(replaceObjectAtIndex:withObject:) withMethod:@selector(hookReplaceObjectAtIndex:withObject:)];
        [obj swizzleInstanceMethod:@selector(removeObjectsInRange:) withMethod:@selector(hookRemoveObjectsInRange:)];
        [obj swizzleInstanceMethod:@selector(subarrayWithRange:) withMethod:@selector(hookSubarrayWithRange:)];
        
    });
}

- (void) hookAddObject:(id)anObject
{
    if (anObject) {
        [self hookAddObject:anObject];
    } else {
        [NSObjectSafe throwException:@"NSMutableArray addObject nil"];
    }
}

- (id) hookObjectAtIndex:(NSUInteger)index
{
    if (index < self.count) {
        return [self hookObjectAtIndex:index];
    }
    [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableArray objectAtIndex [%lu] [%lu]", (unsigned long)self.count, (unsigned long)index]];
    return nil;
}

- (void) hookInsertObject:(id)anObject atIndex:(NSUInteger)index
{
    if (anObject && index <= self.count) {
        [self hookInsertObject:anObject atIndex:index];
    } else {
        if (anObject) {
            [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableArray insertObject [%lu] [%lu]", (unsigned long)self.count, (unsigned long)index]];
        } else {
            [NSObjectSafe throwException:@"NSMutableArray insertObject object is nil"];
        }
    }
}

- (void) hookRemoveObjectAtIndex:(NSUInteger)index
{
    if (index < self.count) {
        [self hookRemoveObjectAtIndex:index];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableArray removeObjectAtIndex [%lu] [%lu]", (unsigned long)self.count, (unsigned long)index]];
    }
}

- (void) hookReplaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject
{
    if (index < self.count && anObject) {
        [self hookReplaceObjectAtIndex:index withObject:anObject];
    } else {
        if (anObject) {
            [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableArray replaceObjectAtIndex [%lu] [%lu]", (unsigned long)self.count, (unsigned long)index]];
        } else {
            [NSObjectSafe throwException:@"NSMutableArray replaceObjectAtIndex object is nil"];
        }
    }
}

- (void) hookRemoveObjectsInRange:(NSRange)range
{
    if (range.location + range.length <= self.count) {
        [self hookRemoveObjectsInRange:range];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableArray removeObjectsInRange [%lu] [%lu-%lu]", (unsigned long)self.count, (unsigned long)range.location, (unsigned long)range.length]];
    }
}

- (NSArray *)hookSubarrayWithRange:(NSRange)range
{
    if (range.location + range.length <= self.count){
        return [self hookSubarrayWithRange:range];
    } else if (range.location < self.count){
        return [self hookSubarrayWithRange:NSMakeRange(range.location, self.count-range.location)];
    }
    [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableArray subarrayWithRange [%lu] [%lu-%lu]", (unsigned long)self.count, (unsigned long)range.location, (unsigned long)range.length]];
    return nil;
}

@end

#pragma mark - NSDictionary Safe

@implementation NSDictionary (Safe)

+ (instancetype) hookDictionaryWithObject:(id)object forKey:(id)key
{
    if (object && key) {
        return [self hookDictionaryWithObject:object forKey:key];
    }
    [NSObjectSafe throwException:[NSString stringWithFormat:@"NSDictionary dictionaryWithObject:forKey %@ is nil", object ? @"key":@"object"]];
    return nil;
}

+ (instancetype) hookDictionaryWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)cnt
{
    NSInteger index = 0;
    id ks[cnt];
    id objs[cnt];
    for (NSInteger i = 0; i < cnt ; ++i) {
        if (keys[i] && objects[i]) {
            ks[index] = keys[i];
            objs[index] = objects[i];
            ++index;
        }
    }
    return [self hookDictionaryWithObjects:objs forKeys:ks count:index];
}

- (id) hookObjectForKey:(id)aKey
{
    if (aKey){
        return [self hookObjectForKey:aKey];
    }
    return nil;
}

@end

#pragma mark - NSMutableDictionary Safe

@implementation NSMutableDictionary (Safe)

- (id) hookObjectForKey:(id)aKey
{
    if (aKey){
        return [self hookObjectForKey:aKey];
    }
    return nil;
}

- (void) hookSetObject:(id)anObject forKey:(id)aKey
{
    if (anObject && aKey) {
        [self hookSetObject:anObject forKey:aKey];
    }
}

- (void) hookRemoveObjectForKey:(id)aKey {
    if (aKey) {
        [self hookRemoveObjectForKey:aKey];
    }
}

@end

#pragma mark - NSObjectSafe Dictionary

@implementation NSObjectSafe (Dictionary)

+ (void) installDictionaryHook
{
    //NSDictionary
    /* 类方法 */
    [NSDictionary swizzleClassMethod:@selector(dictionaryWithObject:forKey:) withMethod:@selector(hookDictionaryWithObject:forKey:)];
    [NSDictionary swizzleClassMethod:@selector(dictionaryWithObjects:forKeys:count:) withMethod:@selector(hookDictionaryWithObjects:forKeys:count:)];
    /* 暂时不重载 */
    //[NSDictionary swizzleClassMethod:@selector(dictionaryWithObjectsAndKeys:) withMethod:@selector(hookDictionaryWithObjectsAndKeys:)];
    
    /* 数组有内容obj类型才是__NSDictionaryI，没内容类型是__NSDictionary0 */
    NSDictionary* obj = [NSDictionary dictionaryWithObjectsAndKeys:@0,@0,nil];
    [obj swizzleInstanceMethod:@selector(objectForKey:) withMethod:@selector(hookObjectForKey:)];
    
    //NSMutableDictionary
    NSMutableDictionary* mutableObj = [[NSMutableDictionary alloc] init];
    [mutableObj swizzleInstanceMethod:@selector(objectForKey:) withMethod:@selector(hookObjectForKey:)];
    [mutableObj swizzleInstanceMethod:@selector(setObject:forKey:) withMethod:@selector(hookSetObject:forKey:)];
    [mutableObj swizzleInstanceMethod:@selector(removeObjectForKey:) withMethod:@selector(hookRemoveObjectForKey:)];
}

@end

#pragma mark - NSSet Safe

@implementation NSSet (Safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 类方法 */
        [NSSet swizzleClassMethod:@selector(setWithObject:) withMethod:@selector(hookSetWithObject:)];
    });
}

+ (instancetype)hookSetWithObject:(id)object
{
    if (object){
        return [self hookSetWithObject:object];
    }
    [NSObjectSafe throwException:@"NSSet setWithObject object is nil"];
    return nil;
}

@end

#pragma mark - NSMutableSet Safe

@implementation NSMutableSet (Safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 普通方法 */
        NSMutableSet* obj = [NSMutableSet setWithObjects:@0, nil];
        [obj swizzleInstanceMethod:@selector(addObject:) withMethod:@selector(hookAddObject:)];
        [obj swizzleInstanceMethod:@selector(removeObject:) withMethod:@selector(hookRemoveObject:)];
    });
}

- (void) hookAddObject:(id)object
{
    if (object) {
        [self hookAddObject:object];
    } else {
        [NSObjectSafe throwException:@"NSMutableSet addObject object is nil"];
    }
}

- (void) hookRemoveObject:(id)object
{
    if (object) {
        [self hookRemoveObject:object];
    } else {
        [NSObjectSafe throwException:@"NSMutableSet removeObject object is nil"];
    }
}

@end

#pragma mark - NSOrderedSet Safe

@implementation NSOrderedSet (Safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 类方法 */
        [NSOrderedSet swizzleClassMethod:@selector(orderedSetWithObject:) withMethod:@selector(hookOrderedSetWithObject:)];
        
        /* init方法:[NSOrderedSet alloc] 和 [NSMutableOrderedSet alloc] 返回的类是一样   */
        NSOrderedSet* obj = [NSOrderedSet alloc];
        [obj swizzleInstanceMethod:@selector(initWithObject:) withMethod:@selector(hookInitWithObject:)];
        //        [obj release];
        
        /* 普通方法 */
        obj = [NSOrderedSet orderedSetWithObjects:@0, nil];
        [obj swizzleInstanceMethod:@selector(objectAtIndex:) withMethod:@selector(hookObjectAtIndex:)];
    });
}

+ (instancetype)hookOrderedSetWithObject:(id)object
{
    if (object) {
        return [self hookOrderedSetWithObject:object];
    } else {
        [NSObjectSafe throwException:@"NSOrderedSet orderedSetWithObject object is nil"];
    }
    return nil;
}

- (instancetype)hookInitWithObject:(id)object
{
    if (object){
        return [self hookInitWithObject:object];
    } else {
        [NSObjectSafe throwException:@"NSOrderedSet initWithObject object is nil"];
    }
    return nil;
}

- (id)hookObjectAtIndex:(NSUInteger)idx
{
    if (idx < self.count){
        return [self hookObjectAtIndex:idx];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSOrderedSet objectAtIndex [%lu] [%lu]", (unsigned long)self.count, (unsigned long)idx]];
    }
    return nil;
}

@end

#pragma mark - NSMutableOrderedSet Safe

@implementation NSMutableOrderedSet (Safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 普通方法 */
        NSMutableOrderedSet* obj = [NSMutableOrderedSet orderedSetWithObjects:@0, nil];
        [obj swizzleInstanceMethod:@selector(objectAtIndex:) withMethod:@selector(hookObjectAtIndex:)];
        [obj swizzleInstanceMethod:@selector(addObject:) withMethod:@selector(hookAddObject:)];
        [obj swizzleInstanceMethod:@selector(removeObjectAtIndex:) withMethod:@selector(hookRemoveObjectAtIndex:)];
        [obj swizzleInstanceMethod:@selector(insertObject:atIndex:) withMethod:@selector(hookInsertObject:atIndex:)];
        [obj swizzleInstanceMethod:@selector(replaceObjectAtIndex:withObject:) withMethod:@selector(hookReplaceObjectAtIndex:withObject:)];
    });
}

- (id)hookObjectAtIndex:(NSUInteger)idx
{
    if (idx < self.count){
        return [self hookObjectAtIndex:idx];
    }
    [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableOrderedSet objectAtIndex [%lu] [%lu]", (unsigned long)self.count, (unsigned long)idx]];
    return nil;
}

- (void)hookAddObject:(id)object
{
    if (object) {
        [self hookAddObject:object];
    } else {
        [NSObjectSafe throwException:@"NSMutableOrderedSet addObject object is nil"];
    }
}

- (void)hookInsertObject:(id)object atIndex:(NSUInteger)idx
{
    if (object && idx <= self.count) {
        [self hookInsertObject:object atIndex:idx];
    } else {
        if (object) {
            [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableOrderedSet insertObject [%lu] [%lu]", (unsigned long)self.count, (unsigned long)idx]];
        } else {
            [NSObjectSafe throwException:@"NSMutableOrderedSet insertObject object is nil"];
        }
    }
}

- (void)hookRemoveObjectAtIndex:(NSUInteger)idx
{
    if (idx < self.count){
        [self hookRemoveObjectAtIndex:idx];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableOrderedSet removeObjectAtIndex [%lu] [%lu]", (unsigned long)self.count, (unsigned long)idx]];
    }
}

- (void)hookReplaceObjectAtIndex:(NSUInteger)idx withObject:(id)object
{
    if (object && idx < self.count) {
        [self hookReplaceObjectAtIndex:idx withObject:object];
    } else {
        if (object) {
            [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableOrderedSet replaceObjectAtIndex [%lu] [%lu]", (unsigned long)self.count, (unsigned long)idx]];
        } else {
            [NSObjectSafe throwException:@"NSMutableOrderedSet replaceObjectAtIndex object is nil"];
        }
    }
}

@end

#pragma mark - NSCache Safe

@implementation NSCache(Safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSCache* obj = [[NSCache alloc] init];
        [obj swizzleInstanceMethod:@selector(setObject:forKey:) withMethod:@selector(hookSetObject:forKey:)];
        [obj swizzleInstanceMethod:@selector(setObject:forKey:cost:) withMethod:@selector(hookSetObject:forKey:cost:)];
    });
}

- (void)hookSetObject:(id)obj forKey:(id)key // 0 cost
{
    if (obj && key) {
        [self hookSetObject:obj forKey:key];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSCache setObject:forKey %@ is nil", obj ? @"key":@"object"]];
    }
}

- (void)hookSetObject:(id)obj forKey:(id)key cost:(NSUInteger)g
{
    if (obj && key) {
        [self hookSetObject:obj forKey:key cost:g];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSCache setObject:forKey:cost %@ is nil", obj ? @"key":@"object"]];
    }
}

@end

#pragma mark - NSString Safe

@implementation NSString (Safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 类方法不用在NSMutableString里再swizz一次 */
        [NSString swizzleClassMethod:@selector(stringWithUTF8String:) withMethod:@selector(hookStringWithUTF8String:)];
        [NSString swizzleClassMethod:@selector(stringWithCString:encoding:) withMethod:@selector(hookStringWithCString:encoding:)];
        
        /* init方法 */
        NSString* obj = [NSString alloc];//NSPlaceholderString
        [obj swizzleInstanceMethod:@selector(initWithCString:encoding:) withMethod:@selector(hookInitWithCString:encoding:)];
        //        [obj release];
        
        /* 普通方法 */
        obj = [[NSString alloc] init];
        [obj swizzleInstanceMethod:@selector(stringByAppendingString:) withMethod:@selector(hookStringByAppendingString:)];
        [obj swizzleInstanceMethod:@selector(substringFromIndex:) withMethod:@selector(hookSubstringFromIndex:)];
        [obj swizzleInstanceMethod:@selector(substringToIndex:) withMethod:@selector(hookSubstringToIndex:)];
        [obj swizzleInstanceMethod:@selector(substringWithRange:) withMethod:@selector(hookSubstringWithRange:)];
    });
}

+ (NSString*) hookStringWithUTF8String:(const char *)nullTerminatedCString
{
    if (NULL != nullTerminatedCString) {
        return [self hookStringWithUTF8String:nullTerminatedCString];
    }
    [NSObjectSafe throwException:@"NSString stringWithUTF8String is nil"];
    return nil;
}

+ (nullable instancetype) hookStringWithCString:(const char *)cString encoding:(NSStringEncoding)enc
{
    if (NULL != cString){
        return [self hookStringWithCString:cString encoding:enc];
    }
    [NSObjectSafe throwException:@"NSString stringWithCString is nil"];
    return nil;
}

- (nullable instancetype) hookInitWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding
{
    if (NULL != nullTerminatedCString){
        return [self hookInitWithCString:nullTerminatedCString encoding:encoding];
    }
    [NSObjectSafe throwException:@"NSString initWithCString is nil"];
    return nil;
}

- (NSString *)hookStringByAppendingString:(NSString *)aString
{
    if (aString){
        return [self hookStringByAppendingString:aString];
    }
    [NSObjectSafe throwException:@"NSString stringByAppendingString is nil"];
    return self;
}

- (NSString *)hookSubstringFromIndex:(NSUInteger)from
{
    if (from <= self.length) {
        return [self hookSubstringFromIndex:from];
    }
    [NSObjectSafe throwException:[NSString stringWithFormat:@"NSString substringFromIndex [%lu] [%lu]", (unsigned long)self.length, (unsigned long)from]];
    return nil;
}

- (NSString *)hookSubstringToIndex:(NSUInteger)to
{
    if (to <= self.length) {
        return [self hookSubstringToIndex:to];
    }
    [NSObjectSafe throwException:[NSString stringWithFormat:@"NSString substringToIndex [%lu] [%lu]", (unsigned long)self.length, (unsigned long)to]];
    return self;
}

- (NSString *)hookSubstringWithRange:(NSRange)range
{
    if (range.location + range.length <= self.length) {
        return [self hookSubstringWithRange:range];
    } else if (range.location < self.length){
        return [self hookSubstringWithRange:NSMakeRange(range.location, self.length-range.location)];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSString substringWithRange [%lu] [%lu-%lu]", (unsigned long)self.length, (unsigned long)range.location, (unsigned long)range.length]];
    }
    return nil;
}

@end

#pragma mark - NSMutableString Safe

@implementation NSMutableString (Safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        /* init方法 */
        NSMutableString* obj = [NSMutableString alloc];//NSPlaceholderMutableString
        [obj swizzleInstanceMethod:@selector(initWithCString:encoding:) withMethod:@selector(hookInitWithCString:encoding:)];
        //        [obj release];
        
        /* 普通方法 */
        obj = [[NSMutableString alloc] init];
        [obj swizzleInstanceMethod:@selector(appendString:) withMethod:@selector(hookAppendString:)];
        [obj swizzleInstanceMethod:@selector(insertString:atIndex:) withMethod:@selector(hookInsertString:atIndex:)];
        [obj swizzleInstanceMethod:@selector(deleteCharactersInRange:) withMethod:@selector(hookDeleteCharactersInRange:)];
        [obj swizzleInstanceMethod:@selector(stringByAppendingString:) withMethod:@selector(hookStringByAppendingString:)];
        [obj swizzleInstanceMethod:@selector(substringFromIndex:) withMethod:@selector(hookSubstringFromIndex:)];
        [obj swizzleInstanceMethod:@selector(substringToIndex:) withMethod:@selector(hookSubstringToIndex:)];
        [obj swizzleInstanceMethod:@selector(substringWithRange:) withMethod:@selector(hookSubstringWithRange:)];
        obj = nil;
    });
}

- (nullable instancetype) hookInitWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding
{
    if (NULL != nullTerminatedCString){
        return [self hookInitWithCString:nullTerminatedCString encoding:encoding];
    } else {
        [NSObjectSafe throwException:@"NSMutableString initWithCString is nil"];
    }
    return nil;
}

- (void) hookAppendString:(NSString *)aString
{
    if (aString){
        [self hookAppendString:aString];
    } else {
        [NSObjectSafe throwException:@"NSMutableString appendString is nil"];
    }
}

- (void) hookInsertString:(NSString *)aString atIndex:(NSUInteger)loc
{
    if (aString && loc <= self.length) {
        [self hookInsertString:aString atIndex:loc];
    } else {
        if (aString) {
            [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableString insertString [%lu] [%lu]", (unsigned long)self.length, (unsigned long)loc]];
        } else {
            [NSObjectSafe throwException:@"NSMutableString insertString string is nil"];
        }
    }
}

- (void) hookDeleteCharactersInRange:(NSRange)range
{
    if (range.location + range.length <= self.length){
        [self hookDeleteCharactersInRange:range];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableString deleteCharactersInRange [%lu] [%lu-%lu]", (unsigned long)self.length, (unsigned long)range.location, (unsigned long)range.length]];
    }
}

- (NSString *)hookStringByAppendingString:(NSString *)aString
{
    if (aString){
        return [self hookStringByAppendingString:aString];
    } else {
        [NSObjectSafe throwException:@"NSMutableString stringByAppendingString is nil"];
    }
    return self;
}

- (NSString *)hookSubstringFromIndex:(NSUInteger)from
{
    if (from <= self.length) {
        return [self hookSubstringFromIndex:from];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableString substringFromIndex [%lu] [%lu]", (unsigned long)self.length, (unsigned long)from]];
    }
    return nil;
}

- (NSString *)hookSubstringToIndex:(NSUInteger)to
{
    if (to <= self.length) {
        return [self hookSubstringToIndex:to];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableString substringToIndex [%lu] [%lu]", (unsigned long)self.length, (unsigned long)to]];
    }
    return self;
}

- (NSString *)hookSubstringWithRange:(NSRange)range
{
    if (range.location + range.length <= self.length) {
        return [self hookSubstringWithRange:range];
    } else if (range.location < self.length){
        return [self hookSubstringWithRange:NSMakeRange(range.location, self.length-range.location)];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSMutableString substringWithRange [%lu] [%lu-%lu]", (unsigned long)self.length, (unsigned long)range.location, (unsigned long)range.length]];
    }
    return nil;
}

@end

#pragma mark - NSUserDefaults Safe

@implementation NSUserDefaults(Safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSUserDefaults* obj = [[NSUserDefaults alloc] init];
        [obj swizzleInstanceMethod:@selector(objectForKey:) withMethod:@selector(hookObjectForKey:)];
        [obj swizzleInstanceMethod:@selector(setObject:forKey:) withMethod:@selector(hookSetObject:forKey:)];
        [obj swizzleInstanceMethod:@selector(removeObjectForKey:) withMethod:@selector(hookRemoveObjectForKey:)];
        
        [obj swizzleInstanceMethod:@selector(integerForKey:) withMethod:@selector(hookIntegerForKey:)];
        [obj swizzleInstanceMethod:@selector(boolForKey:) withMethod:@selector(hookBoolForKey:)];
    });
}

- (id) hookObjectForKey:(NSString *)defaultName
{
    if (defaultName) {
        return [self hookObjectForKey:defaultName];
    } else {
        [NSObjectSafe throwException:@"NSUserDefaults objectForKey key is nil"];
    }
    return nil;
}

- (NSInteger) hookIntegerForKey:(NSString *)defaultName
{
    if (defaultName) {
        return [self hookIntegerForKey:defaultName];
    } else {
        [NSObjectSafe throwException:@"NSUserDefaults integerForKey key is nil"];
    }
    return 0;
}

- (BOOL) hookBoolForKey:(NSString *)defaultName
{
    if (defaultName) {
        return [self hookBoolForKey:defaultName];
    } else {
        [NSObjectSafe throwException:@"NSUserDefaults boolForKey key is nil"];
    }
    return NO;
}

- (void) hookSetObject:(id)value forKey:(NSString*)aKey
{
    if (aKey) {
        [self hookSetObject:value forKey:aKey];
    } else {
        [NSObjectSafe throwException:[NSString stringWithFormat:@"NSUserDefaults boolForKey %@ is nil", value ? @"key":@"value"]];
    }
}

- (void) hookRemoveObjectForKey:(NSString*)aKey
{
    if (aKey) {
        [self hookRemoveObjectForKey:aKey];
    } else {
        [NSObjectSafe throwException:@"NSUserDefaults removeObjectForKey key is nil"];
    }
}

@end

@implementation NSJSONSerialization (Safe)

+ (id) safe_JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error
{
    if (data) {
        return [self JSONObjectWithData:data options:opt error:error];
    }
    [NSObjectSafe throwException:@"NSJSONSerialization JSONObjectWithData data is nil"];
    return nil;
}

+ (NSData *) safe_dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error
{
    if (obj && [NSJSONSerialization isValidJSONObject:obj]) {
        return [self dataWithJSONObject:obj options:opt error:error];
    }
    [NSObjectSafe throwException:@"NSJSONSerialization dataWithJSONObject obj is invalid"];
    return nil;
}

@end
